package secondriver.springsubway.example.dao;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.support.collections.RedisList;
import org.springframework.data.redis.support.collections.RedisZSet;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import secondriver.springsubway.example.bean.Post;
import secondriver.springsubway.example.bean.User;

public class RedisDaoHelper {

	public static final String POST_ID_KEY = "post_id";
	public static final String USER_ID_KEY = "user_id";

	public static final String POST_KEY_PREFIX = "p";
	public static final String TAG_KEY_PREFIX = "t";
	public static final String USER_KEY_PREFIX = "u";

	public static final String RECENT_POSTS_KEY = "recent_posts";
	public static final String POST_CLICKS_KEY = "post_clicks";


	public static final String KEY_SEPARATOR = ":";
	public static final String START = "*";

	public static final int RECENT_POST_MAX_SIZE = 30;

	private RedisTemplate<String, Post> postRedisTemplate;
	private StringRedisTemplate stringRedisTemplate;
	
	private RedisZSet<String> postClicksZSet;
	private RedisList<Post> recentPostsList;

	public String savePost(Post post) {
		String postKey = this.getPostKey();
		post.setPostKey(postKey);
		postRedisTemplate.opsForValue().set(postKey, post);

		this.updatePostClicks(postKey, 1D);
		this.updateRecentPosts(post);
		this.updateTagToPostRelations(post);
		return postKey;
	}

	public String savePostByLuaScript(Post post) {
		String postKey = this.getPostKey();
		post.setPostKey(postKey);

		postRedisTemplate.opsForValue().set(postKey, post);

		// script处理
		DefaultRedisScript<Boolean> script = new DefaultRedisScript<Boolean>();
		script.setResultType(Boolean.class);
		script.setLocation(new DefaultResourceLoader()
				.getResource("classpath:script/save_post.lua"));

		List<String> keys = new ArrayList<String>();
		keys.add(postKey);
		keys.add(RECENT_POSTS_KEY);
		keys.add(POST_CLICKS_KEY);

		postRedisTemplate.execute(script,
				postRedisTemplate.getValueSerializer(), null, keys,
				new Object[] { post });
		// scriptEnd

		this.updateTagToPostRelations(post);
		return postKey;
	}

	public boolean userLogin(User user) {
		Assert.notNull(user, "User not null.");
		String userKey = this.getUserKey(user.getUserName());
		String userPasswd = stringRedisTemplate.opsForValue().get(userKey);
		return !StringUtils.isEmpty(userPasswd)
				&& user.getUserPasswd().equals(userPasswd);
	}

	public boolean userRegister(User user) {
		Assert.notNull(user, "User not null.");
		String userKey = this.getUserKey(user.getUserName());
		if (stringRedisTemplate.hasKey(userKey)) {
			return false;
		} else {
			stringRedisTemplate.opsForValue()
					.set(userKey, user.getUserPasswd());
			return true;
		}
	}

	/**
	 * 更新最近的帖子
	 * 
	 * @param post
	 */
	public void updateRecentPosts(Post post) {
		if (recentPostsList.size() >= RECENT_POST_MAX_SIZE) {
			recentPostsList.removeLast();
		}
		recentPostsList.addFirst(post);
	}

	/**
	 * 更新帖子点击数
	 * 
	 * @param postKey
	 * @param delta
	 * @return
	 */
	public double updatePostClicks(String postKey, double delta) {
		Double score = postClicksZSet.score(postKey);
		if (null == score) {
			score = new Double(1D);
		} else {
			score = score + delta;
		}
		postClicksZSet.add(postKey, score);
		return score;
	}

	/**
	 * 更新Tag中对应的Post
	 * 
	 * @param post
	 */
	public void updateTagToPostRelations(Post post) {
		String postKey = post.getPostKey();
		SetOperations<String, String> setOperations = stringRedisTemplate
				.opsForSet();
		for (String tag : post.getTags()) {
			setOperations.add(this.getTagKey(tag), postKey);
		}
	}

	public boolean isExistTag(String tag) {
		return stringRedisTemplate.hasKey(this.getTagKey(tag));
	}

	public Set<String> getTags() {
		Set<String> tags = stringRedisTemplate.keys(TAG_KEY_PREFIX
				+ KEY_SEPARATOR + START);
		Set<String> noPrefixTags = new HashSet<String>(tags.size());
		int start = TAG_KEY_PREFIX.length() + KEY_SEPARATOR.length();
		for (String tag : tags) {
			noPrefixTags.add(tag.substring(start));
		}
		tags = null;
		return noPrefixTags;
	}

	public Set<String> getPostByRange(double min, double max) {
		return postClicksZSet.rangeByScore(min, max);
	}

	public Post getPostByKey(String postKey) {
		return postRedisTemplate.boundValueOps(postKey).get();
	}

	public List<Post> getPostsByKeys(Collection<String> postKeys) {
		List<Post> posts = new ArrayList<Post>();
		for (String postKey : postKeys) {
			Post post = getPostByKey(postKey);
			if (null != post) {
				posts.add(post);
			}
		}
		return posts;
	}

	public List<Post> getPostByTag(String tag, long count) {
		Set<String> postKeys = stringRedisTemplate.boundSetOps(
				this.getTagKey(tag)).members();

		if (null == postKeys) {
			return Collections.emptyList();
		} else {
			return this.getPostsByKeys(postKeys);
		}
	}

	private Long increment(String key, Long delta) {
		return stringRedisTemplate.opsForValue().increment(key, delta);
	}

	private String getKeyWithSeparator(String keyPrefix, Long id) {
		return getKeyWithSeparator(keyPrefix, String.valueOf(id));
	}

	private String getKeyWithSeparator(String keyPrefix, String id) {
		return keyPrefix + KEY_SEPARATOR + id;
	}

	private Long getPostId() {
		return increment(POST_ID_KEY, 1L);
	}

	public String getPostKey() {
		return getKeyWithSeparator(POST_KEY_PREFIX, getPostId());
	}

	public String getTagKey(String tag) {
		return getKeyWithSeparator(TAG_KEY_PREFIX, tag);
	}

	public String getTag(String tagKey) {
		int start = TAG_KEY_PREFIX.length() + KEY_SEPARATOR.length();
		return tagKey.substring(start);
	}

	public String getUserKey(String userName) {
		return this.getKeyWithSeparator(USER_KEY_PREFIX, userName);
	}

	public RedisTemplate<String, Post> getPostRedisTemplate() {
		return postRedisTemplate;
	}

	public void setPostRedisTemplate(
			RedisTemplate<String, Post> postRedisTemplate) {
		this.postRedisTemplate = postRedisTemplate;
	}

	public StringRedisTemplate getStringRedisTemplate() {
		return stringRedisTemplate;
	}

	public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
		this.stringRedisTemplate = stringRedisTemplate;
	}

	public void setPostClicksZSet(RedisZSet<String> postClicksZSet) {
		this.postClicksZSet = postClicksZSet;
	}

	public RedisList<Post> getRecentPostsList() {
		return recentPostsList;
	}

	public void setRecentPostsList(RedisList<Post> recentPostsList) {
		this.recentPostsList = recentPostsList;
	}

}